EventBus 使用 APT 生成索引以及源码分析

0x0001 配置 EventBus 使用 APT 生成索引

具体可以参见官方文档

通过文档可以看到可以通过三种方式为 项目配置索引:APT、kapt、android-apt,这只是实现功能的不同方式,本文介绍 APT 生成索引,其他两种原理基本相同.

使用 APT 生成索引,不可在匿名内部类中定义订阅方法。要想使用 APT 生成索引,需要对项目进行如下配置:

针对 app 模块下的 build.gradle 文件做以下更改:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
defaultConfig {
javaCompileOptions {
annotationProcessorOptions {
// EventBus Apt 索引类生成位置
arguments = [ eventBusIndex : applicationId + '.EventBusIndex' ]
}
}
}

dependencies {
implementation 'org.greenrobot:eventbus:3.1.1'
// 添加注解处理器依赖
annotationProcessor 'org.greenrobot:eventbus-annotation-processor:3.1.1'
}

kapt 下的配置:

1
2
3
4
5
6
7
8
9
10
11
12
apply plugin: 'kotlin-kapt' // ensure kapt plugin is applied

dependencies {
implementation 'org.greenrobot:eventbus:3.1.1'
kapt 'org.greenrobot:eventbus-annotation-processor:3.1.1'
}

kapt {
arguments {
arg('eventBusIndex', 'com.example.myapp.MyEventBusIndex')
}
}

在项目自定义的 Application 中添加如下代码:

1
2
3
4
5
6
7
8
@Override
public void onCreate() {
super.onCreate();
EventBus.builder()
.ignoreGeneratedIndex(false) // 使用 Apt 插件
.addIndex(new EventBusIndex()) // 添加索引类
.installDefaultEventBus(); // 作为默认配置
}

0x0002 索引类

重新构建项目,则可以项目 build 相关文件夹下生成 EventBusIndex 源文件,大致如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/** This class is generated by EventBus, do not edit. */
public class MyEventBusIndex implements SubscriberInfoIndex {
private static final Map<Class<?>, SubscriberInfo> SUBSCRIBER_INDEX;

static {
SUBSCRIBER_INDEX = new HashMap<Class<?>, SubscriberInfo>();
// 这些信息是如何的生成?是根据项目中存在订阅者的类生成的。
putIndex(new SimpleSubscriberInfo(EventBusStickyActtivity.class, true, new SubscriberMethodInfo[] {
new SubscriberMethodInfo("onTestEvent", EventBusEvent.class, ThreadMode.MAIN, 0, true),
new SubscriberMethodInfo("onTestEventSec", EventBusEvent.class, ThreadMode.BACKGROUND),
}));

}

private static void putIndex(SubscriberInfo info) {
SUBSCRIBER_INDEX.put(info.getSubscriberClass(), info);
}

@Override
public SubscriberInfo getSubscriberInfo(Class<?> subscriberClass) {
SubscriberInfo info = SUBSCRIBER_INDEX.get(subscriberClass);
if (info != null) {
return info;
} else {
return null;
}
}
}

0x0003 索引类中相关代码分析

重点查看一下静态代码块中对 putIndex 方法的调用,传入订阅者信息,并以订阅者为 key 将订阅者存入 SUBSCRIBER_INDEX 中。

SimpleSubscriberInfo 的继承关系:

1
2
3
4
5
6
7
8
9
public interface SubscriberInfo {
Class<?> getSubscriberClass();
SubscriberMethod[] getSubscriberMethods();
SubscriberInfo getSuperSubscriberInfo();
boolean shouldCheckSuperclass();
}

public abstract class AbstractSubscriberInfo implements SubscriberInfo {}
public class SimpleSubscriberInfo extends AbstractSubscriberInfo{}

SimpleSubscriberInfo 的构造函数如下:

1
2
3
4
5
6
7
8
9
/**
* subscriberClass:订阅者
* shouldCheckSuperclass: 是否检查父类的标志位
* methodInfos: 订阅者中中订阅方法的数组集合
*/
public SimpleSubscriberInfo(Class subscriberClass, boolean shouldCheckSuperclass, SubscriberMethodInfo[] methodInfos) {
super(subscriberClass, null, shouldCheckSuperclass);
this.methodInfos = methodInfos;
}

SubscriberMethodInfo 的构造函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* methodName: 方法名。如上面代码中的 onTestEvent、onTestEventSec
* eventType: 事件的类型,EventBusEvent.class
* threadMode: 事件类型处理的线程模式
* priority: 优先级
* sticky: 是否为粘性事件
**/
public SubscriberMethodInfo(String methodName, Class<?> eventType, ThreadMode threadMode,
int priority, boolean sticky) {
this.methodName = methodName;
this.threadMode = threadMode;
this.eventType = eventType;
this.priority = priority;
this.sticky = sticky;
}

最终通过调用 getSubscriberInfo 方法来获得 SubscriberInfo(其实在这里是 SimpleSubscriberInfo 对象)

0x0004 使生成的索引生效

以上主要分析了 APT 生成的索引类的信息,那么如何使这些信息生效,具体分析如下。

在自定义 Application 中我们对EventBus 以及生成的索引做了如下初始化:

1
2
3
4
EventBus.builder()
.ignoreGeneratedIndex(false)
.addIndex(new MyEventBusIndex()) // 添加索引类
.installDefaultEventBus();

EventBus.builder() 生成了 EventBusBuilder 对象。

看一下具体的 addIndex 做了什么:

EventBusBuilder#addIndex

1
2
3
4
5
6
7
public EventBusBuilder addIndex(SubscriberInfoIndex index) {
if (subscriberInfoIndexes == null) {
subscriberInfoIndexes = new ArrayList<>();
}
subscriberInfoIndexes.add(index);
return this;
}

以上代码为向 subscriberInfoIndexes 添加具体的索引,此时 EventBusBuilder 对象已经持有 索引 的信息。

EventBusBuilder#installDefaultEventBus 对 EventBus 对象进行初始化,并赋值给 EventBus.defaultInstance。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public EventBus installDefaultEventBus() {
synchronized (EventBus.class) {
if (EventBus.defaultInstance != null) {
throw new EventBusException("Default instance already exists." +
" It may be only set once before it's used the first time to ensure consistent behavior.");
}
// 生成 EventBus 对象
EventBus.defaultInstance = build();
return EventBus.defaultInstance;
}
}
/** Builds an EventBus based on the current configuration. */
public EventBus build() {
return new EventBus(this);
}

至此,EventBus 已经完成了初始化,由于其在进程中为单例,所以此后 通过 EventBus.getDefault() 获得 EventBus 对象皆为此处所配置。

答案就是将 EventBus 设计为进程单例,那么在 Application 为 EventBus 对象做的一切配置都是全局生效的。

以下为将此生成的索引与 EventBus 建立连接。

1
2
3
4
5
6
7
8
EventBus(EventBusBuilder builder) {
....
indexCount = builder.subscriberInfoIndexes != null ? builder.subscriberInfoIndexes.size() : 0;
// 在此初始化 SubscriberMethodFinder, 并为之配置 索引
subscriberMethodFinder = new SubscriberMethodFinder(builder.subscriberInfoIndexes,
builder.strictMethodVerification, builder.ignoreGeneratedIndex);
...
}

至此,通过 EventBusBuilder 对象生成的索引信息就会被配置到 EventBus 的属性 SubscriberMethodFinder 上,如上所说,通过 EventBus.getDefault() 获取到的 EventBus 都持有 APT 生成的索引的信息。

在此后查询订阅者的订阅方法,那么在接下来的步骤中 getSubscriberInfo() 方法返回值不为空,并会调用索引类中的 getSubscriberInfo 来返回订阅者信息,从而可以获取订阅方法。

SubscriberMethodFinder#findUsingInfo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
private List<SubscriberMethod> findUsingInfo(Class<?> subscriberClass) {
FindState findState = prepareFindState();
findState.initForSubscriber(subscriberClass);
while (findState.clazz != null) {
// 如果使用索引,那么此处值不为 null
findState.subscriberInfo = getSubscriberInfo(findState);
if (findState.subscriberInfo != null) {
// 通过 findState.subscriberInfo.getSubscriberMethods() 则可以直接获得索引类生成的订阅方法数组,相较于通过反射获取订阅者的订阅方法,效率大大提高。
SubscriberMethod[] array = findState.subscriberInfo.getSubscriberMethods();
for (SubscriberMethod subscriberMethod : array) {
if (findState.checkAdd(subscriberMethod.method, subscriberMethod.eventType)) {
findState.subscriberMethods.add(subscriberMethod);
}
}
} else {
findUsingReflectionInSingleClass(findState);
}
findState.moveToSuperclass();
}
return getMethodsAndRelease(findState);
}

SubscriberMethodFinder#getSubscriberInfo 获取订阅信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private SubscriberInfo getSubscriberInfo(FindState findState) {
if (findState.subscriberInfo != null && findState.subscriberInfo.getSuperSubscriberInfo() != null) {
SubscriberInfo superclassInfo = findState.subscriberInfo.getSuperSubscriberInfo();
if (findState.clazz == superclassInfo.getSubscriberClass()) {
return superclassInfo;
}
}
if (subscriberInfoIndexes != null) {
// 使用索引,则 subscriberInfoIndexes 不为空,index.getSubscriberInfo(findState.clazz) 获取的即为 索引类中 静态方法块中通过 putIndex 设置的订阅信息
for (SubscriberInfoIndex index : subscriberInfoIndexes) {
SubscriberInfo info = index.getSubscriberInfo(findState.clazz);
if (info != null) {
return info;
}
}
}
return null;
}

0x0005 使用 APT 生成索引类的优点

如上面所说,在编译期通过 APT 生成的索引类,即在编译期就获取了订阅者的信息(所有的订阅者以及其中的订阅方法),从而取代在代码执行期(运行期)通过反射获得订阅方法的动作,效率大大提高。